package com.seventhdawn.gwt.rcx.client;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter;
import com.google.gwt.user.client.rpc.impl.Serializer;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * Client-side {@link com.google.gwt.user.client.rpc.RemoteService
 * RemoteService} proxy superclass that includes RPC context payload
 * into each RPC request.
 * <p>
 * RPC context payload is physically stored at the end of RPC request
 * body. A special {@link #CONTEXT_PAYLOAD_LENGTH header} is used to
 * indicate the length of RPC context payload.
 * <p>
 * <b>Important notice:</b> When using this proxy superclass, be sure
 * to apply {@link com.seventhdawn.gwt.rcx.server.RPCContextFilter
 * RPCContextFilter} <em>before your server-side RPC service implementation</em>
 * (e.g. a {@link RemoteServiceServlet} subclass) takes over the RPC request
 * processing.
 * 
 * @see RPCClientContext
 * @author vojtech.szocs
 */
public abstract class RPCContextServiceProxy extends RemoteServiceProxy {
    
    /**
     * Name of the HTTP header that holds the length of RPC context payload.
     */
    public static final String CONTEXT_PAYLOAD_LENGTH = "rpc-context-length";
    
    protected RPCContextServiceProxy(String moduleBaseURL,
            String remoteServiceRelativePath, String serializationPolicyName,
            Serializer serializer) {
        super(moduleBaseURL, remoteServiceRelativePath,
                serializationPolicyName, serializer);
    }
    
    /**
     * Configures a {@link RequestBuilder} to send an RPC request.
     * <p>
     * Each RPC request includes the RPC context payload as well
     * as the call-specific RPC payload itself.
     */
    protected <T> RequestBuilder doPrepareRequestBuilderImpl(
            ResponseReader responseReader, String methodName,
            int invocationCount, String requestData, AsyncCallback<T> callback) {
        
        if (getServiceEntryPoint() == null) {
            throw new NoServiceEntryPointSpecifiedException();
        }
        
        RequestCallbackAdapter<T> responseHandler = new RequestCallbackAdapter<T>(
                this, methodName, invocationCount, callback, responseReader);
        
        RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
                getServiceEntryPoint());
        
        rb.setHeader("Content-Type", "text/x-gwt-rpc; charset=utf-8");
        rb.setCallback(responseHandler);
        
        // serialize RPC context object
        ClientSerializationStreamWriter streamWriter = createStreamWriter();
        try {
            streamWriter.writeObject(RPCClientContext.get());
        } catch (SerializationException ex) {
            InvocationException iex = new InvocationException(
                    "Unable to initiate the asynchronous service invocation -- error while serializing RPC context object",
                    ex);
            throw iex;
        }
        String rpcContextPayload = streamWriter.toString();
        
        StringBuilder requestDataBuilder = new StringBuilder(requestData);
        requestDataBuilder.append(rpcContextPayload);
        
        // update RPC request to include RPC context payload
        rb.setRequestData(requestDataBuilder.toString());
        rb.setHeader(CONTEXT_PAYLOAD_LENGTH, String.valueOf(rpcContextPayload.length()));
        
        return rb;
    }
    
    @Override
    protected <T> Request doInvoke(ResponseReader responseReader,
            String methodName, int invocationCount, String requestData,
            AsyncCallback<T> callback) {
        
        RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader,
                methodName, invocationCount, requestData, callback);
        
        try {
            return rb.send();
        } catch (RequestException ex) {
            InvocationException iex = new InvocationException(
                    "Unable to initiate the asynchronous service invocation -- check the network connection",
                    ex);
            callback.onFailure(iex);
        } finally {
            if (RemoteServiceProxy.isStatsAvailable()
                    && RemoteServiceProxy.stats(RemoteServiceProxy.bytesStat(
                            methodName, invocationCount, requestData.length(),
                            "requestSent"))) {
            }
        }
        
        return null;
    }
    
    @Override
    protected <T> RequestBuilder doPrepareRequestBuilder(
            ResponseReader responseReader, String methodName,
            int invocationCount, String requestData, AsyncCallback<T> callback) {
        
        RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader,
                methodName, invocationCount, requestData, callback);
        
        return rb;
    }
    
}
